package demo.messaging.client.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;

import demo.messaging.client.core.MessageEvent.EnumMessageEventType;
import demo.messaging.common.AbstractMessage;
import demo.messaging.common.ControlMessage;
import demo.messaging.common.EnumUserStatus;
import demo.messaging.common.IMessageService;
import demo.messaging.common.IMessageServiceAsync;
import demo.messaging.common.ControlMessage.EnumControlType;

public class ClientManager {

	/**
	 * ID of the bean in the application context.
	 */
	public static final String BEAN_ID = "messagingClientManager";

	/**
	 * Shortcut for  ApplicationContext.getBean(BEAN_ID).
	 * @return
	 */
	public static ClientManager getClientManager() {
		return (ClientManager) ApplicationContext
				.getBean(ClientManager.BEAN_ID);
	}

	private EnumUserStatus userStatus = EnumUserStatus.free;

	private final Timer userListLoaderTimer;

	private final Timer messageLoaderTimer;

	/**
	 * RPC proxy.
	 */
	private final IMessageServiceAsync messageService = GWT
			.create(IMessageService.class);

	private Map<String, EnumUserStatus> mapUser = new HashMap<String, EnumUserStatus>();

	private final List<AbstractMessage> listMessage = new ArrayList<AbstractMessage>();

	private final List<IMessageEventListener> eventListenners = new ArrayList<IMessageEventListener>();

	private String userName;

	private String currentConnectedUser;

	/**
	 * 
	 */
	private String currentInvitor;

	private String currentGuest;

	/**
	 * The game choice selected by the invitor.
	 */
	private String gameChoice;

	public ClientManager() {
		this.messageLoaderTimer = new Timer() {

			@Override
			public void run() {
				loadMessage();

			}

		};

		this.userListLoaderTimer = new Timer() {
			@Override
			public void run() {
				loadUserList();
			}

		};
	}

	private void dispatchEvent(final AbstractMessage message) {
		Log.debug(this.userName + " dispatchEvent" + " currentInvitor="
				+ this.currentInvitor + " currentGuest=" + this.currentGuest
				+ " currentConnectedUser=" + this.currentConnectedUser);

		if (message instanceof ControlMessage) {
			ControlMessage control = (ControlMessage) message;
			if (control.getType() == EnumControlType.invite) {

				receiveInvitation(control);

			} else if (control.getType() == EnumControlType.acceptInvite
					&& control.getSender().equals(this.currentGuest)) {

				receiveAcceptance(control);

			} else if (control.getType() == EnumControlType.cancelConnection
					&& (
					// guest declines the invitation				
					control.getSender().equals(this.currentGuest)

					// invitor cancels invitation
							|| control.getSender().equals(this.currentInvitor)

					// the user ends the connection.
					|| control.getSender().equals(this.currentConnectedUser)

					)) {

				receiveCancel(control);

			} else if (control.getType() == EnumControlType.busy
					&& control.getSender().equals(this.currentGuest)) {

				receiveBusyMessage(control);

			} else {

				Log.debug(this.userName + " unexpected control message: "
						+ control);
			}
		} else {
			fireEvent(new MessageEvent(
					EnumMessageEventType.applicationDataReceived, message));
		}
	}

	public void endConnection() {
		sendCancelMessage(this.currentConnectedUser);

	}

	private void enterInteractiveMode() {
		this.userListLoaderTimer.cancel();
		this.messageLoaderTimer
				.scheduleRepeating(EnumTimerRefreshInterval.interactive
						.getIntervalMs());
	}

	private void enterStandbyMode() {

		// load the user list immediately for the first time.
		loadUserList();

		this.userListLoaderTimer
				.scheduleRepeating(EnumTimerRefreshInterval.standby
						.getIntervalMs());
		this.messageLoaderTimer
				.scheduleRepeating(EnumTimerRefreshInterval.standby
						.getIntervalMs());
	}

	private void fireEvent(final MessageEvent event) {

		Log.debug(this.userName
				+ " fireEvent "
				+ event.getType()

				+ ((null == event.getMessage()) ? "" : (" message= " + event
						.getMessage())));

		if (null != event.getThrowable()) {
			Log.debug("RPC error", event.getThrowable());
		}
		for (IMessageEventListener l : this.eventListenners) {
			try {
				l.onMessageEvent(event);
			} catch (Exception e) {
				Log.error("error", e);
			}
		}
	}

	public String getCurrentConnectedUser() {
		return this.currentConnectedUser;
	}

	public String getCurrentGuest() {
		return this.currentGuest;
	}

	public String getCurrentInvitor() {
		return this.currentInvitor;
	}

	public List<IMessageEventListener> getEventListeners() {
		return this.eventListenners;
	}

	public String getGameChoice() {
		return this.gameChoice;
	}

	public String getLogin() {
		return this.userName;
	}

	public Map<String, EnumUserStatus> getMapUser() {
		return this.mapUser;
	}

	private void loadMessage() {
		this.messageService
				.getMessages(new AsyncCallback<List<? extends AbstractMessage>>() {

					@Override
					public void onFailure(final Throwable caught) {
						fireEvent(new MessageEvent(
								EnumMessageEventType.rpcError, caught));

					}

					@Override
					public void onSuccess(
							final List<? extends AbstractMessage> result) {
						ClientManager.this.listMessage.addAll(result);

						processMessages();
					}
				});
	}

	private void loadUserList() {
		this.messageService
				.listUsers(new AsyncCallback<Map<String, EnumUserStatus>>() {

					@Override
					public void onFailure(final Throwable caught) {
						fireEvent(new MessageEvent(
								EnumMessageEventType.rpcError, caught));

					}

					@Override
					public void onSuccess(
							final Map<String, EnumUserStatus> result) {

						if (!ClientManager.this.mapUser.equals(result)) {
							ClientManager.this.mapUser = result;

							fireEvent(new MessageEvent(
									EnumMessageEventType.listUserUpdated));

						}
					}
				});

	}

	public void login(final String login) {

		Log.debug("login " + login);

		this.messageService.login(login, new AsyncCallback<Void>() {

			@Override
			public void onFailure(final Throwable caught) {
				fireEvent(new MessageEvent(EnumMessageEventType.rpcError,
						caught));

			}

			@Override
			public void onSuccess(final Void result) {
				ClientManager.this.userName = login;

				enterStandbyMode();

				fireEvent(new MessageEvent(EnumMessageEventType.loginSuccess));

			}
		});

	}

	public void logout() {
		Log.debug("logout " + this.userName);

		this.messageService.logout(this.userName, null);
	}

	private void processMessages() {

		if (!this.listMessage.isEmpty()) {
			Log.debug(this.userName + " processMessages" + " currentInvitor="
					+ this.currentInvitor + " currentGuest="
					+ this.currentGuest + " currentConnectedUser="
					+ this.currentConnectedUser);
			Log.debug(this.userName + " messages=" + this.listMessage);
		}

		for (AbstractMessage m : new ArrayList<AbstractMessage>(
				this.listMessage)) {
			if ((this.userStatus == EnumUserStatus.busy)
					&& !m.getSender().equals(this.currentConnectedUser)
					&& !m.getSender().equals(this.currentInvitor)
					&& !m.getSender().equals(this.currentGuest)) {
				ControlMessage reply = new ControlMessage(EnumControlType.busy);
				reply.setDestination(m.getSender());
				sendMessageAsync(reply);

			} else {

				dispatchEvent(m);

			}
			this.listMessage.remove(m);
		}
	}

	private void receiveAcceptance(final ControlMessage control) {
		updateUserStatus(EnumUserStatus.busy);
		this.currentConnectedUser = this.currentGuest;
		this.currentGuest = null;

		enterInteractiveMode();

		fireEvent(new MessageEvent(EnumMessageEventType.acceptanceReceived,
				control));
	}

	private void receiveBusyMessage(final ControlMessage control) {
		updateUserStatus(EnumUserStatus.free);
		this.currentGuest = null;

		fireEvent(new MessageEvent(EnumMessageEventType.busyMessageReceived,
				control));
	}

	private void receiveCancel(final ControlMessage control) {
		updateUserStatus(EnumUserStatus.free);
		this.currentGuest = null;
		this.currentInvitor = null;

		enterStandbyMode();

		fireEvent(new MessageEvent(EnumMessageEventType.cancelReceived, control));
	}

	private void receiveInvitation(final ControlMessage control) {
		updateUserStatus(EnumUserStatus.busy);
		this.currentInvitor = control.getSender();
		this.gameChoice = control.getGameChoice();

		fireEvent(new MessageEvent(EnumMessageEventType.invitationReceived,
				control));
	}

	/**
	 * Send acceptance to the current invitor.
	 */
	public void sendAcceptance() {

		updateUserStatus(EnumUserStatus.busy);

		final ControlMessage m = new ControlMessage(
				EnumControlType.acceptInvite);
		m.setDestination(this.currentInvitor);

		sendMessage(m, new AsyncCallback<Void>() {

			@Override
			public void onFailure(final Throwable caught) {
				fireEvent(new MessageEvent(EnumMessageEventType.rpcError, m,
						caught));

			}

			@Override
			public void onSuccess(final Void result) {
				ClientManager.this.currentConnectedUser = ClientManager.this.currentInvitor;
				ClientManager.this.currentInvitor = null;

				enterInteractiveMode();

				fireEvent(new MessageEvent(EnumMessageEventType.acceptanceSent,
						m));
			}

		});

	}

	/**
	 * Send a decline message to currentInvitor or currentGuest.
	 */
	private void sendCancelMessage(final String user) {
		updateUserStatus(EnumUserStatus.free);

		final ControlMessage m = new ControlMessage(
				EnumControlType.cancelConnection);
		m.setDestination(user);

		sendMessage(m, new AsyncCallback<Void>() {

			@Override
			public void onFailure(final Throwable caught) {
				fireEvent(new MessageEvent(EnumMessageEventType.rpcError, m,
						caught));
			}

			@Override
			public void onSuccess(final Void result) {
				ClientManager.this.currentInvitor = null;
				ClientManager.this.currentGuest = null;

				enterStandbyMode();

				fireEvent(new MessageEvent(EnumMessageEventType.cancelSent, m));
			}

		});

	}

	/**
	 * Send a decline message to currentGuest.
	 */
	public void sendCancelMessageToGuest() {
		sendCancelMessage(this.currentGuest);
	}

	/**
	 * Send a decline message to currentInvitor.
	 */
	public void sendCancelMessageToInvitor() {
		sendCancelMessage(this.currentInvitor);
	}

	public void sendInvitation(final ControlMessage message) {

		this.updateUserStatus(EnumUserStatus.busy);

		sendMessage(message, new AsyncCallback<Void>() {

			@Override
			public void onFailure(final Throwable caught) {
				fireEvent(new MessageEvent(EnumMessageEventType.rpcError,
						message, caught));
			}

			@Override
			public void onSuccess(final Void result) {
				ClientManager.this.currentGuest = message.getDestination();
				enterInteractiveMode();
				ClientManager.this.gameChoice = message.getGameChoice();
				fireEvent(new MessageEvent(EnumMessageEventType.invitationSent,
						message));
			}

		});

	}

	public void sendMessage(final AbstractMessage message,
			final AsyncCallback<Void> callback) {
		message.setSender(this.userName);
		this.messageService.sendMessage(message, callback);
	}

	public void sendMessageAsync(final AbstractMessage message) {
		this.messageService.sendMessage(message, new AsyncCallback<Void>() {

			@Override
			public void onFailure(final Throwable caught) {
				fireEvent(new MessageEvent(EnumMessageEventType.rpcError,
						message, caught));
			}

			@Override
			public void onSuccess(final Void result) {
			}
		});
	}

	public void setLogin(final String userName) {
		this.userName = userName;
	}

	private void updateUserStatus(final EnumUserStatus status) {
		if (this.userStatus != status) {

			this.userStatus = status;
			this.messageService.updateUserStatus(status,
					new AsyncCallback<Void>() {

						@Override
						public void onFailure(final Throwable caught) {
							fireEvent(new MessageEvent(
									EnumMessageEventType.rpcError, caught));
						}

						@Override
						public void onSuccess(final Void result) {
						}

					});

		}

	}

}
